package com.spatialkey.datapoller;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerBean;
import org.springframework.util.Log4jConfigurer;

import com.spatialkey.datapoller.spring.ModularConfiguration;

/**
 * Copyright (c) 2009 Universal Mind, Inc.
 * @author Anthony McClure
 * Date: May 8, 2009
 * Time: 10:31:36 AM
 *
 */
public class Main
{
	private static Logger logger = Logger.getLogger("com.spatialkey.datapoller.Main");
	
    /**
     * The main entry point method.
     * 
     * @param args	application args (not used).
     */
    public static void main(String[] args) 
    {
    	
        // register the shutdown hook
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
				endApp();
            }
        });;
   	
    	// This block forces the app to stop if the properties file isn't found.
    	// Otherwise, the initLogging call will just ignore a bad path, which isn't what we want.
    	try {
    		if (!(new File("spatialkey-datapoller.properties").exists())) {
    			throw new FileNotFoundException();
    		}
        	Log4jConfigurer.initLogging("spatialkey-datapoller.properties");
    		logger.trace("[main] Enter");
    	} catch (FileNotFoundException ex) {
    		System.out.println("Couldn't find the \"spatialkey-datapoller.properties\" file!");
    		System.exit(-1);
    	}
    	
    	logger.info("[SpatialKey Data Poller] Starting...");

    	// Set up initial Spring context with common beans, and any others added to the configurationFileList
        FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("spatialkey-datapoller.cfg/spatialkey-datapoller.xml");
        if (ctx.containsBean("configurationFileList")) {
        	ModularConfiguration configurationFileList = (ModularConfiguration) ctx.getBean("configurationFileList");
        	ctx = new FileSystemXmlApplicationContext(configurationFileList.getConfigurationFileList(), true, ctx);
        }
        
        logger.info("About to start scheduling engine...");
        
        //get the quartzFactory bean
        Scheduler scheduler = (Scheduler) ctx.getBean("scheduleFactory");
        
    	// Read the spatialkey-datapublisher.tasks directory, looking for tasks
        File taskDir = new File("spatialkey-datapoller.tasks");
        FilenameFilter filter = new FilenameFilter() {
        	public boolean accept(File dir, String name) {
        		return name.endsWith(".xml");
        	}
    	}; 
        
        SAXReader xmlReader = new SAXReader();
        ArrayList<DataPollingTask> runTheseNow = new ArrayList<DataPollingTask>();
        int counter = 0;
        for (File task : taskDir.listFiles(filter))
        {
        	try {
				//load the task into the application context
        		String[] tmp = {"spatialkey-datapoller.tasks/" + task.getName()};
				ctx = new FileSystemXmlApplicationContext(tmp, true, ctx);
				
				//load the xml data
				Document xml = xmlReader.read(task);
				
				HashMap<String, String> map = new HashMap<String, String>();
				map.put( "dft", "http://www.springframework.org/schema/beans");
				
				XPath taskNameXPath = DocumentHelper.createXPath("//dft:bean[@class='com.spatialkey.datapoller.DataPollingTask']/@id");
				taskNameXPath.setNamespaceURIs(map);
				Node node = taskNameXPath.selectSingleNode(xml);
				String taskName = node.getText();
				
				//get the task to run
				DataPollingTask dpTask = (DataPollingTask) ctx.getBean(taskName);
				String scheduleType = dpTask.getScheduleType();
				String runAtStartup = dpTask.getRunAtStartup();
				
				MethodInvokingJobDetailFactoryBean jobDetail;
				//create appropriate schedule
				if (scheduleType != null)
				{
					if (scheduleType.equals("simple"))
					{
						try {
							if (dpTask.getInterval() <= 0)
							{
								logger.error("Simple timer task: " + taskName + " cannot start as it is missing an interval.");
								continue;
							}
							
							long interval = dpTask.getInterval() * 60000;
							
							runAtStartup = "false"; //happens automatically with a simple timer
							
							//create job
							jobDetail = new MethodInvokingJobDetailFactoryBean();
							jobDetail.setTargetObject(dpTask);
							jobDetail.setTargetMethod("run");
							jobDetail.setName(taskName);
							jobDetail.setConcurrent(false);
							jobDetail.afterPropertiesSet();
							
							//create trigger
							SimpleTriggerBean trigger = new SimpleTriggerBean();
							trigger.setBeanName(taskName);
							trigger.setJobDetail((JobDetail) jobDetail.getObject());
							trigger.setRepeatInterval(interval);
							trigger.afterPropertiesSet();
							
							//add to schedule
							scheduler.scheduleJob((JobDetail) jobDetail.getObject(), trigger);
							
							logger.info("Simple timer task: " + taskName + " scheduled.");
							
							counter++;
						} catch (Exception e) {
							logger.error("Schedule: " + taskName + " was not created because of an error: " + e.getMessage());
							e.printStackTrace();
							continue;
						}
					}
					else if (scheduleType.equals("cron"))
					{
						try {
							if (dpTask.getCronExpression() == null || dpTask.getCronExpression().trim().length() == 0)
							{
								logger.error("Cron timer task: " + taskName + " cannot start as it is missing an expression.");
								continue;
							}
							
							//create job
							jobDetail = new MethodInvokingJobDetailFactoryBean();
							jobDetail.setTargetObject(dpTask);
							jobDetail.setTargetMethod("run");
							jobDetail.setName(taskName);
							jobDetail.setConcurrent(false);
							jobDetail.afterPropertiesSet();
							
							//create cron trigger
							CronTriggerBean trigger = new CronTriggerBean();
							trigger.setBeanName(taskName);
							trigger.setJobDetail((JobDetail) jobDetail.getObject());
							trigger.setCronExpression(dpTask.getCronExpression());
							trigger.afterPropertiesSet();
							
							//add to schedule
							scheduler.scheduleJob((JobDetail) jobDetail.getObject(), trigger);
							
							logger.info("Cron timer task: " + taskName + " scheduled.");
							
							counter++;
						} catch (Exception e) {
							logger.error("Schedule: " + taskName + " was not created because of an error: " + e.getMessage());
							e.printStackTrace();
							continue;
						}
					}
					else
					{
						logger.error("Schedule type of: " + scheduleType + " is not a valid type. In task: " + taskName);
						continue;
					}
				}
				else
				{
					logger.error("No schedule type was included for task: " + taskName);
					continue;
				}
				
				//add to run now list if needed
				if (runAtStartup.toLowerCase().equals("true"))
					runTheseNow.add(dpTask);
			} catch (Exception e) {
				logger.error("The task file: " + task.getName() + " was not scheduled do to a problem: " + e.getMessage());
				e.printStackTrace();
				continue;
			}
        }
        
    	switch (counter) {
    	case 0:
    		logger.error("No tasks have been scheduled. Check your configurations and logs for details!");
    		System.exit(-1);
    	case 1:
    		logger.info("1 task has been scheduled.");
    		break;
    	default:
    		logger.info(counter + " tasks have been scheduled.");
    	}
    	
    	//do the run now tasks
        for (DataPollingTask dpTask : runTheseNow)
        {
        	dpTask.run();
        }
    }
    
    /**
     * The end application hook, so we can log if the application is stopped abruptly.
     */
    public static void endApp() {
		logger.warn("[main] The data poller was shut down! Please verify that it has been restarted.");
    }
    
}
